package com.container.app.utilities;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

/**
 * Created with IntelliJ IDEA.
 * User: Georgiana.Scarlat
 * Date: 7/27/12
 * Time: 12:27 PM
 */
public final class PackageClasses {
    /**
     * Scans all classes accessible from the context class loader which belong to the given package and subpackages.
     *
     * @param packageName The base package
     * @return The classes
     * @throws ClassNotFoundException
     * @throws java.io.IOException
     */

    public static List<Class> getClasses(String packageName) throws ClassNotFoundException, IOException {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        assert classLoader != null;
        String path = packageName.replace('.', '/');

        Enumeration<URL> resources = classLoader.getResources(path);
        List<File> dirs = new ArrayList<File>();
        List<String> jars = new ArrayList<String>();

        while (resources.hasMoreElements()) {
            URL resource = resources.nextElement();

            String fileName = resource.getFile();
            if ((fileName.indexOf("!") > 0) && (fileName.indexOf(".jar") > 0)) {
                String jarPath = fileName.substring(0, fileName.indexOf("!"))
                        .substring(fileName.indexOf(":") + 1);
                jarPath = URLDecoder.decode(jarPath, "UTF-8");
                jars.add(jarPath);
            } else {
                fileName = URLDecoder.decode(fileName, "UTF-8");
                dirs.add(new File(fileName));
            }

        }

        List<Class> classes = new ArrayList<Class>();
        for (File directory : dirs) {
            classes.addAll(findClasses(directory, packageName));
        }
        for (String jar : jars) {
            classes.addAll(getClassesFromJar(jar, packageName));
        }
        return classes;
    }

    /**
     * Recursive method used to find all classes in a given directory and subdirs.
     *
     * @param directory   The base directory
     * @param packageName The package name for classes found inside the base directory
     * @return The classes
     * @throws ClassNotFoundException
     */

    private static List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException {
        List<Class> classes = new ArrayList<Class>();
        if (!directory.exists()) {
            return classes;
        }
        File[] files = directory.listFiles();
        for (File file : files) {
            String fileName = file.getName();
            if (file.isDirectory()) {
                assert !fileName.contains(".");
                classes.addAll(findClasses(file, packageName + "." + fileName));
            } else if (fileName.endsWith(".class") && !fileName.contains("$")) {
                Class _class;
                try {
                    _class = Class.forName(packageName + '.' + fileName.substring(0, fileName.length() - 6));
                } catch (ExceptionInInitializerError e) {

                    _class = Class.forName(packageName + '.' + fileName.substring(0, fileName.length() - 6), false, Thread.currentThread().getContextClassLoader());
                }
                classes.add(_class);
            }
        }
        return classes;
    }

    public static List<Class> getClassesFromJar(String jar, String packageName) throws IOException, ClassNotFoundException {
        List<Class> result = new LinkedList<Class>();
        JarInputStream jarInputStream = new JarInputStream(new FileInputStream(jar));
        JarEntry jarEntry;

        while (true) {
            jarEntry = jarInputStream.getNextJarEntry();

            if (jarEntry == null) break;

            String className = jarEntry.getName();

            if (className.endsWith(".class") && !className.contains("$")) {
                className = className.substring(0, className.length() - 6);
                if (className.startsWith(packageName.replace('.', '/')))

                    result.add(Class.forName(className.replace('/', '.')));
            }
        }
        return result;
    }


}
